﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MW5.Api.Helpers;
using MW5.Api.Interfaces;
using MW5.Api.Static;
using MW5.Plugins.Interfaces;
using MW5.Plugins.Services;
using MW5.Services.Concrete;
using MW5.Shared;
using MW5.Tools.Helpers;
using MW5.Tools.Model;
using MW5.Tools.Model.Layers;
using MW5.Tools.Model.Parameters.Layers;

namespace MW5.Tools.Services
{
    public class OutputManager
    {
        private readonly ILayerService _layerService;

        public OutputManager(ILayerService layerService)
        {
            if (layerService == null) throw new ArgumentNullException("layerService");
            _layerService = layerService;
        }

        /// <summary>
        /// Saves datasource or (and) adds it to the map depending on options specified in OutputLayerInfo instance.
        /// </summary>
        public bool Save(IDatasource ds, OutputLayerInfo outputInfo)
        {
            ds.Callback = null;

            if (outputInfo.MemoryLayer)
            {
                return HandleMemoryOutput(ds, outputInfo);
            }

            return HandleDiskOutput(ds, outputInfo);
        }

        public bool AddToMap(ILayerSource source)
        {
            return _layerService.AddDatasource(source);
        }

        /// <summary>
        /// Handles output generated by GDAL utilities. The location of output 
        /// is specified by OutputLayerInfo.Filename;
        /// </summary>
        public bool HandleGdalOutput(OutputLayerInfo outputInfo)
        {
            if (File.Exists(outputInfo.Filename))
            {
                outputInfo.DatasourcePointer = new DatasourcePointer(outputInfo.Filename);

                if (outputInfo.AddToMap)
                {
                    // don't report error if layer isn't added to the map
                    // user might cancel it because of projection mismatch
                    _layerService.AddLayersFromFilename(outputInfo.Filename);
                }

                return true;
            }

            return false;
        }

        private bool HandleDiskOutput(IDatasource ds, OutputLayerInfo outputInfo)
        {
            string filename = outputInfo.Filename;

            if (File.Exists(filename) && !outputInfo.Overwrite)
            {
                return HandleOverwriteFailure();
            }

            bool result = SaveDatasource(ds, filename);

            ds.Dispose();
            outputInfo.Result = null;

            if (!result)
            {
                return false;
            }

            outputInfo.DatasourcePointer = new DatasourcePointer(filename);

            if (outputInfo.AddToMap)
            {
                // don't report error if layer isn't added to the map
                // user might cancel it because of projection mismatch
                _layerService.AddLayersFromFilename(filename);
            }

            return true;
        }

        private bool HandleMemoryOutput(IDatasource ds, OutputLayerInfo outputInfo)
        {
            if (!ds.IsVector)
            {
                throw new ApplicationException("Memory layers can only be used for vector datasources.");
            }

            if (!outputInfo.AddToMap)
            {
                throw new ApplicationException("Memory layer option can only be used with add to map option.");
            }

            bool result = _layerService.AddDatasource(ds, outputInfo.Name);
            if (result)
            {
                outputInfo.DatasourcePointer = new DatasourcePointer(_layerService.LastLayerHandle, outputInfo.Name);
                outputInfo.Result = null;
            }
            else
            {
                if (outputInfo.Result != null)
                {
                    outputInfo.Result.Dispose();
                    outputInfo.Result = null;
                }
            }

            // report failure if we haven't added datasource to the map
            return result;
        }

        private bool HandleOverwriteFailure()
        {
            // TODO: implement
            return false;
        }

        private bool SaveDatasource(IDatasource ds, string filename)
        {
            if (!GeoSource.Remove(filename))
            {
                return HandleOverwriteFailure();
            }

            if (LayerSourceHelper.Save(ds, filename))
            {
                Logger.Current.Info("Layer ({0}) is created.", filename);
                return true;
            }

            Logger.Current.Error("Failed to save datasource: " + ds.LastError);
            return false;
        }

        public bool DeleteOutputs(IParametrizedTool tool)
        {
            foreach (var output in tool.GetOutputs())
            {
                if (File.Exists(output.Filename) && output.Overwrite)
                {
                    if (!GeoSource.Remove(output.Filename))
                    {
                        MessageService.Current.Info("Failed to remove datasource: " + output.Filename);
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Clones the input datasource, saves it to the disk and starts append mode.
        /// </summary>
        public bool SaveAppendModeFeatureSet(IFeatureSet fs, OutputLayerInfo output, IToolLogger log)
        {
            if (File.Exists(output.Filename))
            {
                if (output.Overwrite)
                {
                    if (!GeoSource.Remove(output.Filename))
                    {
                        log.Info("Failed to overwrite.");
                        return false;
                    }
                }
                else
                {
                    log.Info("Overwrite options isn't selected.");
                    return false;
                }
            }

            if (!fs.SaveAsEx(output.Filename, true))
            {
                log.Info("Failed to save resulting datasource: " + fs.LastError);
                return false;
            }

            fs.StartAppendMode();

            return true;
        }
    }
}
